<template>
  <div class="markdown-text" ref="mark" v-loading="loading">
    <!--markdown主体编译器
      v-model：绑定文档内容
      :mode：编辑模式
      @save：保存文档
      :default-show-toc：是否显示目录导航
      :disabled-menus：禁用菜单
      height：高度
      @copy-code-success：复制代码成功钩子函数
      @change：文档内容改变钩子函数
      left-toolbar：左侧工具栏
      @upload-image：上传图片钩子函数
      :toc-nav-position-right：目录导航是否在右侧
    -->
    <v-md-editor
        v-model="markDownText"
        :mode="props.markMode"
        @save="saveMarkdown"
        :default-show-toc="true"
        :disabled-menus="[]"
        height="100%"
        @copy-code-success="copyCodeSuccess"
        @change="textChange"
        left-toolbar="undo redo clear | h bold italic strikethrough quote tip | ul ol table hr | link image code | save "
        @upload-image="uploadMarkdownImage"
        :toc-nav-position-right="true"></v-md-editor>
    <Transition name="card" mode="out-in">
      <!--标题目录-->
      <el-card class="markdown-title" shadow="hover" v-if="titleList.length!==0&&props.markMode==='preview'">
        <div class="title-text">
          <div v-for="(anchor,index) in titleList" :key="anchor.title"
               @click="rollTo(anchor,index)"
               :class="index===highLightTitle?`md-title title-active`:'md-title'"
               :style="{ padding: `10px 0 10px ${anchor.indent * 3}rem` }">
            <a :class="`md-title-text  md-h${anchor.indent}`">{{ anchor.title }}</a>
          </div>
        </div>
      </el-card>
    </Transition>
  </div>
</template>
<script setup>
import {computed, onBeforeMount, watch, onMounted, ref, watchEffect, onBeforeUnmount, getCurrentInstance} from "vue";
import {ElMessage, ElNotification} from "element-plus";
import request from "@/api/request";
import {getToken} from "@/utils/auth";

const props = defineProps({
  fileUrl: String,  //文件名
  markMode: {  //模式
    type: String,
    default: 'preview'
  }
})
//获取当前实例
const {proxy} = getCurrentInstance()
//路由
const route = proxy.$route
//页面最大宽度
const maxWith = ref('114rem')
//文档内容
const markDownText = ref('')
//文档旧内容
const markDownOldText = ref('')
const mark = ref(null)
//标题列表
const titleList = ref([])
//高亮标题
const highLightTitle = ref(0)
const loading = ref(false)
//文档名
const markdownFilename = ref(props.fileUrl)
//保存文件定时器
const timer = ref()
// 自动保存开关，默认为关
const autoSaveFlag = ref(false)

//监听文件名变化
watch(() => props.fileUrl, (newVal) => {
  //获取文件名
  markdownFilename.value = newVal
  //解析文档内容
  previewMarkdown(markdownFilename.value)
  setTimeout(() => {
    getTitle()
  }, 500)
})
//页面最大宽度
const maxWidth = computed(() => {
  return props.markMode === 'preview' ? '84%' : '100%'
})
//页面最大高度
const markHeight = computed(() => {
  return props.markMode === 'preview' ? 'unset' : '100%'
})

// 页面渲染前解析文档内容
onBeforeMount(() => {
  previewMarkdown(markdownFilename.value)
})
// 监听路由变化从而改变页面最大宽度
watchEffect(() => {
  if (route.name === 'document') {
    maxWith.value = 'unset'
  } else {
    maxWith.value = '114rem'
  }
})

onMounted(() => {
  //键盘监听
  document.addEventListener('keydown', keyDownSave, false)
  //滚动监听
  mark.value.addEventListener('scroll', scrollHandler())
  //获取标题
  setTimeout(() => {
    getTitle()
  }, 500)
})

onBeforeUnmount(() => {
  //清除定时器
  if (timer.value) {
    clearTimeout(timer.value)
  }
  //移除键盘监听
  document.removeEventListener('keydown', keyDownSave, false)
  //移除滚动监听
  mark.value.removeEventListener('scroll', scrollHandler())
})


const textChange = () => {
  if (!autoSaveFlag.value) {
    return
  }
  //文档内容改变时，3秒后自动保存
  if (timer.value) {
    clearTimeout(timer.value)
  }
  if (props.markMode === 'preview') {
    return
  }
  //判断文本是否为空或全为空格
  if (markDownText.value === '' || markDownText.value.trim() === '') {
    return
  }
  timer.value = setTimeout(() => {
    if (markDownText.value !== markDownOldText.value) {
      saveMarkdown()
    } else {
      console.log("无需保存")
    }
  }, 3000)
}

const keyDownSave = (e) => {
  if ((e.ctrlKey || e.metaKey) && e.key === 's') {
    // 执行save方法
    saveMarkdown()
    // 阻止默认事件
    e.preventDefault()
  }
}

//保存文件
const saveMarkdown = async () => {
  //判断文本是否为空或全为空格
  if (markDownText.value === '' || markDownText.value.trim() === '') {
    ElMessage({
      message: '文件内容不能为空',
      type: 'error',
    })
    return
  }
  //判断文件是否改动
  if (markDownText.value === markDownOldText.value) {
    ElMessage({
      message: '文件未改动，无需保存',
      type: 'info',
    })
    return
  }
  //字符串转Blob
  let export_blob = new Blob([markDownText.value]);
  //Blob转File
  let file = new File([export_blob], markdownFilename.value.split('/')[3])
  try {
    //上传markdown文件
    const res = await request({
      url: '/admin/front/uploadMarkdown',
      method: 'post',
      data: {
        file: file,
        type: 'markdown',
      },
      timeout: 300000,
      headers: {
        Authorization: 'Bearer ' + getToken(),
        'Content-Type': 'multipart/form-data'
      },
    })
    if (res.code === 200) {
      ElNotification({
        title: 'success',
        message: '文件更改已保存',
        type: 'success',
        duration: 2000,
        offset: 100,
      })
      //保存成功后更新旧内容
      markDownOldText.value = markDownText.value
    } else {
      ElNotification({
        title: 'error',
        message: '文件保存失败',
        type: 'error',
        duration: 2000,
        offset: 100,
      })
    }
  } catch (e) {
    return e
  }
}
// 复制成功后逻辑
const copyCodeSuccess = (code) => {
  if (code) {
    ElMessage({
      message: '代码已复制',
      type: 'success',
    })
  }
}

//上传图片
const uploadMarkdownImage = async (event, insertImage, files) => {
  for (const file of files) {
    //判断图片大小
    if (file.size > 5 * 1024 * 1024) {
      ElMessage({
        message: '图片大小不能超过5M',
        type: 'error',
      })
    } else {
      const res = await request({
        url: '/admin/front/uploadMarkdown',
        method: 'post',
        data: {
          file: file,
          type: 'image',
        },
        timeout: 300000,
        headers: {
          Authorization: 'Bearer ' + getToken(),
          'Content-Type': 'multipart/form-data'
        },
      })
      if (res.code === 200) {
        //插入图片
        insertImage({
          url: process.env.VUE_APP_PUBLIC_NETWORK + res.msg,
          desc: file.name
        })
      } else {
        ElMessage({
          message: '添加图片' + file.name + '失败',
          type: 'error',
        })
      }
    }
  }
}
//滚动事件处理器
const scrollHandler = () => {
  if (props.markMode !== 'preview') {
    return
  }
  //监听屏幕滚动时防抖
  let timeOut = null
  return () => {
    clearTimeout(timeOut)
    timeOut = setTimeout(() => {
      //获取滚动高度
      let scrollTop = mark.value.scrollTop
      const absList = []
      //计算每个标题与滚动高度的差值
      titleList.value.forEach((item) => {
        absList.push(Math.abs(item.height - scrollTop))
      })
      //获取最小差值的标题索引
      highLightTitle.value = absList.indexOf(Math.min.apply(null, absList))
    }, 200)
  }
}
//滚动条滚动到指定标题
const rollTo = (anchor, index) => {
  //获取标题索引
  const {lineIndex} = anchor
  //获取标题元素
  const heading = mark.value.querySelector(
      `.v-md-editor-preview [data-v-md-line="${lineIndex}"]`
  )
  //滚动到标题
  if (heading) {
    heading.scrollIntoView({behavior: 'smooth', block: 'start'})
  }
  //设置高亮标题
  highLightTitle.value = index
}
//获取标题
const getTitle = () => {
  //获取标题元素
  const anchors = mark.value.querySelectorAll(
      '.markdown-body h1,h2,h3,h4,h5,h6'
  )
  // img设置盗链，展示第三方图片
  mark.value.querySelectorAll(
      'img'
  ).forEach((item) => {
    item.setAttribute('referrerPolicy', 'no-referrer')
  })
  //过滤标题
  const titles = Array.from(anchors).filter((title) => !!title.innerText.trim());
  if (!titles.length) {
    titleList.value = [];
    return;
  }
  //获取标题层级
  const hTags = Array.from(new Set(titles.map((title) => title.tagName))).sort();
  //设置标题数据
  titleList.value = titles.map((el) => ({
    title: el.innerText, // 标题内容
    lineIndex: el.getAttribute('data-v-md-line'), // 标签line id
    indent: hTags.indexOf(el.tagName), // 标签层级
    height: el.offsetTop, // 标签距离顶部距离
  }))
}
//预览markdown
const previewMarkdown = (filename) => {
  //获取markdown文件
  fetch(process.env.VUE_APP_PUBLIC_NETWORK + filename, {
    method: 'get',
    mode: 'cors',
    credentials: "include",
    cache: "no-cache",
    headers: {
      'Content-type': 'text/plain'
    }
  }).then(async function (res) {
    res.text().then((data) => {
      //设置markdown内容
      markDownText.value = data
      markDownOldText.value = data
    })
  })
}
</script>
<style scoped lang="scss">
.markdown-text {
  height: 100%;
  overflow-y: scroll;
  font-size: 2rem;
  background: white;
}

.v-md-editor {
  box-sizing: border-box;
  height: v-bind(markHeight) !important;
  max-width: v-bind(maxWidth);
  padding-left: 2rem;
  margin: unset;
}

.document-wrap .v-md-editor{
  max-width: v-bind(maxWidth) !important;
  box-sizing: border-box;
  overflow-y: hidden !important;
}


:deep(.v-md-pre-wrapper) {
  max-width: 90% !important;
}

:deep(.v-md-pre-wrapper pre) {
  max-height: 70rem !important;
}

:deep(.github-markdown-body table) {
  max-width: 90rem !important;
}

:deep(.github-markdown-body) {
  max-width: v-bind(maxWith);
  padding: 10px;
}

:deep(.github-markdown-body img) {
  max-width: 100rem !important;
}

.markdown-title {
  position: absolute;
  top: 15%;
  right: 4%;
  border-radius: 1rem;
  padding: 1rem;
  font-size: 1.5rem;
  height: 69vh;
  width: 17%;
  overflow-y: scroll;

  :deep(.el-card__body ) {
    padding: 0;
    height: inherit;
  }
}

.document-wrap .markdown-title{
  top: 10% !important;
}

.title-active {
  background-color: #1a9b92a1;
  color: white;
  border-radius: 4px;
  z-index: 9;
}

.md-title-text {
  cursor: pointer;
  margin-right: 1rem;
}

.md-h0 {
  padding-left: 1rem;
}

.card-enter-active {
  transition: all 0.5s;
}

.card-leave-active {
  transition: all 0s;
}

.card-enter-from,
.card-leave-to {
  opacity: 0;
  transform: translateY(30px);
}


</style>